/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

const { reject } = require('lodash')

//创建一个promise构造器
// 这里将promise状态定义好
const PENDING = 'pengding' //等待
const FULFILLED = 'fulfilled' //成功
const REJECTED = 'rejected' //失败
class ljlPromise {
  //promise 是个类，new出来以后会立即执行，这里我们创建一个执行器,执行器接受一个函数
  constructor(fn) {
    // 由于是调用自身，所以this调用
    //promise自执行时候 try catch捕获异常 reject返回
    try {
      fn(this.resolve, this.reject)
    } catch (e) {
      this.reject(e)
    }
  }
  //定义状态
  status = PENDING //promise 刚执行的时候一定是等待状态
  value = undefined // 接收成功返回值
  reason = undefined //接收失败返回值

  sucssessCallback = [] //这里为了接收pending状态的方法定义好的接收返回成功函数的数组
  failCallback = []
  //定义静态方法all
  static all(array) {
    let result = []
    let index = 0
    function addData(key, value) {
      result[key] = value
    }
    return new ljlPromise((resolve, reject) => {
      //定义一个计数器，延迟resolve返回操作
      for (let i = 0; i < array.length; i++) {
        index++
        //循环传入的数组，依次判断是传入的是promise中的对象还是值
        if (array[i] instanceof ljlPromise) {
          array[i].then(
            (val) => {
              addData(i, value)
            },
            (err) => {
              reject(err)
            }
          )
        } else {
          addData(i, array[i])
        }
      }
      if (index === array.length) {
        resolve(result)
      }
    })
  }
  static catch (callback) {
    return this.then(value => {
        return MyPromise.resolve(callback()).then(() => value);
      }, reason => {
        return MyPromise.resolve(callback()).then(() => { throw reason })
      })
  }
  static finally (failCallback) {
    return this.then(undefined, failCallback)
  }
  //这里定义两种状态函数，用来接受返回状态
  resolve = (value) => {
    //当调用resolve时，promise只能等待状态变为其他两种状态，判断是否等待状态
    if (this.status !== PENDING) {
      return
    }
    this.status = FULFILLED //更改状态成功
    this.value = value //成功赋值
    //有时候我们在promise中调用异步代码延时器等待返回时，promise处于pending状态，这时候我们就要执行我们在then中定义好的两个回调
    while (this.sucssessCallback.length) {
      this.sucssessCallback.shift()(this.value)
    }
  }
  reject = (value) => {
    if (this.status !== PENDING) {
      return
    }
    this.status = REJECTED //更改状态失败
    this.reason = value //失败赋值
    while (this.failCallback.length) {
      this.failCallback.shift()(this.reason)
    }
  }
  //   定义then方法 接收两个回掉，成功和失败回调
  then = (sucssessCallback, failCallback) => {
    // 将其参数变为可选参数
    sucssessCallback = sucssessCallback
      ? sucssessCallback
      : value => value;
        
    failCallback = failCallback
      ? failCallback
      : (reason) => {
          throw  reason
        }

    //这里将整个函数体调用new Promise包裹，然后将结果return出去，形成链式调用
    let promise1 = new ljlPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        //   由于需要判断方法中是否调用了自身，所有用setTimeOut异步执行，返回结果后处理

        setTimeout(() => {
          try {
            let x = sucssessCallback(this.value)
            //    promise链式下一个then方法会接收上一then返回的值或者自身resolve方法，所以这里需要进行判断
            resolvePromise(x, promise1, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let y = failCallback(this.reason)
            resolvePromise(y, promise1, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      } else {
        //pending状态将两个回调储存起来
        this.sucssessCallback.push(sucssessCallback)
        this.failCallback.push(failCallback)
      }
    })
    return promise1
  }
}
// 这里定义一个公用方法，用来解决
//
function resolvePromise(x, promise1, resolve, reject) {
  //判断是否调用了自身
  if (x === promise1) {
    throw Error('promise 被自己调用了')
    return
  }
  //判断传入的x是否属于ljlPromise类，属于则执行then,普通返回值则直接resolve

  if (x instanceof ljlPromise) {
    x.then(resolve, reject)
  } else {
    resolve(x)
  }
}
let promise = new ljlPromise((resolve, reject) => {
  resolve('1000')
})
  .then(
    (res) => {
      setTimeout(() => {
        console.log(res)
      }, 10)
    },
    (err) => {
      console.log(err)
    }
  )
  .then((res) => {
    setTimeout(() => {
      console.log(res)
    }, 10)
  })
